<?php

declare(strict_types=1);

namespace app\service\order;

use app\model\Order;
use app\model\Shipment;
use app\model\ShipmentCompany;
use app\model\ShipmentTrace;
use app\service\notify\CreateNotifyService;
use app\util\GenNoUtil;
use InvalidArgumentException;
use think\facade\Db;
use think\Paginator;

class ShipmentService
{
    public function __construct(private CreateNotifyService $notifyService)
    {
    }

    public function paginate(array $filters, int $page, int $pageSize): Paginator
    {
        $query = Shipment::with(['company'])
            ->order('id', 'desc');

        if (!empty($filters['keyword'])) {
            $keyword = trim((string) $filters['keyword']);
            $query->where(function ($subQuery) use ($keyword) {
                $subQuery->whereLike('shipment_no', '%' . $keyword . '%')
                    ->whereOr('order_no', 'like', '%' . $keyword . '%')
                    ->whereOr('company_name', 'like', '%' . $keyword . '%');
            });
        }

        if ($filters['status'] ?? '' !== '') {
            $query->where('status', (int) $filters['status']);
        }

        foreach (['order_id', 'seller_org_id', 'buyer_org_id', 'company_id'] as $field) {
            if ($filters[$field] ?? '' !== '') {
                $query->where($field, (int) $filters[$field]);
            }
        }

        return $query->paginate([
            'list_rows' => $pageSize,
            'page' => $page,
        ]);
    }

    public function findById(int $id): ?Shipment
    {
        return Shipment::with(['company', 'traces'])->find($id);
    }

    public function create(array $payload): Shipment
    {
        $data = $this->extractPayload($payload, true);
        $shipmentNo = trim((string) ($data['shipment_no'] ?? ''));
        if ($shipmentNo === '') {
            $shipmentNo = GenNoUtil::shipment();
            $data['shipment_no'] = $shipmentNo;
        }

        $order = $this->ensureRelations($data);
        $this->assertShipmentNoUnique($shipmentNo, null);

        $traces = $payload['traces'] ?? [];

        /** @var Shipment $shipment */
        $shipment = Db::transaction(function () use ($data, $traces, $order) {
            /** @var Shipment $shipment */
            $shipment = Shipment::create($data);

            if (is_array($traces) && $traces !== []) {
                $this->replaceTraces($shipment, $traces);
            }

            if (isset($data['assist_user_id'])) {
                $order->assist_user_id = (int) $data['assist_user_id'];
            }

            if ((int) $order->status < Order::STATUS_PENDING_SELLER_SHIP) {
                $order->status = Order::STATUS_PENDING_SELLER_SHIP;
            }

            $order->save();

            return $shipment;
        });

        $shipment = $this->findById((int) $shipment->id);

        if ($shipment && ($orderSnapshot = $order->refresh())) {
            $this->syncOrderStatusByShipment($orderSnapshot, $shipment);
            $this->notifySellerOnShipment($orderSnapshot, $shipment);
        }

        return $shipment;
    }

    public function update(Shipment $shipment, array $payload): Shipment
    {
        $data = $this->extractPayload($payload, false);
        $order = $this->ensureRelations($data, (int) $shipment->order_id);

        if (isset($payload['shipment_no'])) {
            $shipmentNo = trim((string) $payload['shipment_no']);
            if ($shipmentNo === '') {
                throw new InvalidArgumentException('运单号不能为空');
            }
            if ($shipmentNo !== $shipment->shipment_no) {
                $this->assertShipmentNoUnique($shipmentNo, (int) $shipment->id);
            }
            $data['shipment_no'] = $shipmentNo;
        }

        $originalCompanyId = (int) ($shipment->company_id ?? 0);
        $originalCompanyName = (string) ($shipment->company_name ?? '');

        Db::transaction(function () use ($shipment, $data, $payload) {
            $shipment->save($data);
            if (array_key_exists('traces', $payload) && is_array($payload['traces'])) {
                $this->replaceTraces($shipment, $payload['traces']);
            }
        });

        $updated = $this->findById((int) $shipment->id);
        if ($updated && ($orderSnapshot = $order->refresh())) {
            $this->syncOrderStatusByShipment($orderSnapshot, $updated);
            if ($this->shouldNotifyOnAssign($originalCompanyId, $originalCompanyName, $updated)) {
                $this->notifySellerOnShipment($orderSnapshot, $updated);
            }
        }

        return $updated;
    }

    public function delete(Shipment $shipment): bool
    {
        return (bool) Db::transaction(function () use ($shipment) {
            ShipmentTrace::where('shipment_id', $shipment->id)->delete();
            return $shipment->delete();
        });
    }

    public function format(Shipment $shipment): array
    {
        return [
            'id' => (int) $shipment->id,
            'order_id' => (int) $shipment->order_id,
            'order_no' => $shipment->order_no,
            'seller_org_id' => (int) $shipment->seller_org_id,
            'buyer_org_id' => (int) $shipment->buyer_org_id,
            'company_id' => $shipment->company_id ? (int) $shipment->company_id : null,
            'company_name' => $shipment->company_name,
            'shipment_no' => $shipment->shipment_no,
            'is_cod' => (int) $shipment->is_cod,
            'freight_amount' => (float) $shipment->freight_amount,
            'freight_currency' => $shipment->freight_currency,
            'freight_remark' => $shipment->freight_remark,
            'assist_user_id' => $shipment->assist_user_id ? (int) $shipment->assist_user_id : null,
            'pickup_address' => $shipment->pickup_address,
            'pickup_time' => $shipment->pickup_time,
            'weight' => (float) $shipment->weight,
            'package_count' => (int) $shipment->package_count,
            'consignee' => $shipment->consignee,
            'consignee_phone' => $shipment->consignee_phone,
            'consignee_addr' => $shipment->consignee_addr,
            'ship_time' => $shipment->ship_time,
            'delivery_time' => $shipment->delivery_time,
            'signed_time' => $shipment->signed_time,
            'status' => (int) $shipment->status,
            'status_label' => $shipment->status_label,
            'remark' => $shipment->remark,
            'extra_data' => $shipment->extra_data ?? [],
            'create_time' => $shipment->create_time,
            'update_time' => $shipment->update_time,
            'company' => $shipment->company ? [
                'id' => (int) $shipment->company->id,
                'name' => $shipment->company->name,
                'code' => $shipment->company->code,
            ] : null,
            'traces' => array_map(static function (ShipmentTrace $trace) {
                return [
                    'id' => (int) $trace->id,
                    'trace_time' => $trace->trace_time,
                    'status' => (int) $trace->status,
                    'location' => $trace->location,
                    'detail' => $trace->detail,
                    'operator' => $trace->operator,
                    'raw_data' => $trace->raw_data ?? [],
                ];
            }, $shipment->traces ? $shipment->traces->all() : []),
        ];
    }

    public function formatCollection(iterable $shipments): array
    {
        $results = [];
        foreach ($shipments as $shipment) {
            if ($shipment instanceof Shipment) {
                $results[] = $this->format($shipment);
            }
        }

        return $results;
    }

    private function extractPayload(array $payload, bool $isCreate): array
    {
        $fields = [
            'order_id',
            'order_no',
            'seller_org_id',
            'buyer_org_id',
            'company_id',
            'company_name',
            'shipment_no',
            'is_cod',
            'freight_amount',
            'freight_currency',
            'freight_remark',
            'assist_user_id',
            'pickup_address',
            'pickup_time',
            'weight',
            'package_count',
            'consignee',
            'consignee_phone',
            'consignee_addr',
            'ship_time',
            'delivery_time',
            'signed_time',
            'status',
            'remark',
            'extra_data',
        ];

        $data = array_intersect_key($payload, array_flip($fields));

        foreach (['order_id', 'seller_org_id', 'buyer_org_id', 'company_id', 'assist_user_id', 'package_count'] as $intField) {
            if (array_key_exists($intField, $data) && $data[$intField] !== null && $data[$intField] !== '') {
                $data[$intField] = (int) $data[$intField];
            }
        }

        foreach (['freight_amount', 'weight'] as $floatField) {
            if (array_key_exists($floatField, $data) && $data[$floatField] !== null && $data[$floatField] !== '') {
                $data[$floatField] = (float) $data[$floatField];
            }
        }

        if (array_key_exists('is_cod', $data)) {
            $data['is_cod'] = (int) $data['is_cod'];
        }

        if (array_key_exists('status', $data)) {
            $data['status'] = (int) $data['status'];
        }

        if ($isCreate) {
            $data += [
                'is_cod' => $data['is_cod'] ?? 1,
                'package_count' => $data['package_count'] ?? 1,
                'status' => $data['status'] ?? 0,
                'freight_amount' => $data['freight_amount'] ?? 0,
            ];
        }

        return $data;
    }

    public function ensurePickupTask(Order $order): Shipment
    {
        $existing = Shipment::where('order_id', $order->id)
            ->whereNull('delete_time')
            ->order('id', 'desc')
            ->find();

        if ($existing) {
            return $existing;
        }

        return Shipment::create([
            'order_id' => $order->id,
            'order_no' => $order->order_no,
            'seller_org_id' => $order->seller_org_id,
            'buyer_org_id' => $order->buyer_org_id,
            'assist_user_id' => $order->assist_user_id,
            'shipment_no' => GenNoUtil::shipment(),
            'status' => Shipment::STATUS_PENDING_PICKUP,
        ]);
    }

    private function ensureRelations(array &$data, ?int $orderId = null): Order
    {
        $targetOrderId = $data['order_id'] ?? $orderId;
        if (!$targetOrderId) {
            throw new InvalidArgumentException('关联订单不存在');
        }

        $order = Order::where('id', $targetOrderId)->find();
        if (!$order) {
            throw new InvalidArgumentException('关联订单不存在');
        }

        if (isset($data['company_id']) && $data['company_id']) {
            $company = ShipmentCompany::where('id', $data['company_id'])->find();
            if (!$company) {
                throw new InvalidArgumentException('物流公司不存在');
            }
            if (!isset($data['company_name']) || trim((string) $data['company_name']) === '') {
                $data['company_name'] = $company->name;
            }
        }

        return $order;
    }

    private function replaceTraces(Shipment $shipment, array $traces): void
    {
        ShipmentTrace::where('shipment_id', $shipment->id)->delete();
        foreach ($traces as $trace) {
            if (!is_array($trace)) {
                continue;
            }

            $payload = [
                'shipment_id' => $shipment->id,
                'trace_time' => $trace['trace_time'] ?? null,
                'status' => isset($trace['status']) ? (int) $trace['status'] : 0,
                'location' => $trace['location'] ?? '',
                'detail' => $trace['detail'] ?? '',
                'operator' => $trace['operator'] ?? '',
                'raw_data' => $trace['raw_data'] ?? [],
            ];

            ShipmentTrace::create($payload);
        }
    }

    private function assertShipmentNoUnique(string $shipmentNo, ?int $excludeId = null): void
    {
        if ($shipmentNo === '') {
            return;
        }

        $query = Shipment::where('shipment_no', $shipmentNo);
        if ($excludeId !== null) {
            $query->where('id', '<>', $excludeId);
        }

        if ($query->count() > 0) {
            throw new InvalidArgumentException('运单号已存在');
        }
    }

    private function syncOrderStatusByShipment(Order $order, Shipment $shipment): void
    {
        $shipmentStatus = (int) $shipment->status;

        if ($shipmentStatus <= Shipment::STATUS_PICKED_UP) {
            if ($order->status < Order::STATUS_PENDING_SELLER_SHIP) {
                $order->status = Order::STATUS_PENDING_SELLER_SHIP;
                $order->save();
            }
            return;
        }

        if (in_array($shipmentStatus, [Shipment::STATUS_IN_TRANSIT, Shipment::STATUS_DELIVERING], true)) {
            if ($order->status < Order::STATUS_IN_TRANSIT) {
                $order->status = Order::STATUS_IN_TRANSIT;
                $order->ship_status = max((int) $order->ship_status, 1);
                $order->save();
            }
            return;
        }

        if ($shipmentStatus === Shipment::STATUS_SIGNED) {
            $order->status = Order::STATUS_COMPLETED;
            $order->finish_status = 1;
            $order->ship_status = 2;
            $order->save();
        }
    }

    private function shouldNotifyOnAssign(int $originalCompanyId, string $originalCompanyName, Shipment $updated): bool
    {
        $hadCompany = $originalCompanyId > 0 || trim($originalCompanyName) !== '';
        $hasCompanyNow = ((int) ($updated->company_id ?? 0) > 0) || trim((string) ($updated->company_name ?? '')) !== '';

        return $hasCompanyNow && !$hadCompany;
    }

    private function notifySellerOnShipment(Order $order, Shipment $shipment): void
    {
        $hasCompany = ((int) ($shipment->company_id ?? 0) > 0) || trim((string) ($shipment->company_name ?? '')) !== '';
        if (!$hasCompany) {
            return;
        }

        $targets = [];
        if ($order->seller_user_id) {
            $targets[] = [
                'id' => (int) $order->seller_user_id,
                'type' => 'seller',
            ];
        }

        if ($targets === []) {
            return;
        }

        $this->notifyService->add($targets, 'shipment_arranged', [
            'order_no' => $order->order_no,
            'shipment_no' => $shipment->shipment_no,
            'company_name' => $shipment->company_name,
            'consignee' => $shipment->consignee,
            'consignee_addr' => $shipment->consignee_addr,
        ]);
    }
}
